<html lang="en-US">

<head>
    
<title>深入理解 Go panic and recover - 花落雨忧</title>

<meta property="og:title" content="深入理解 Go panic and recover - 花落雨忧">



    



    
    <meta property="description" content="作为一个 gophper，我相信你对于 panic 和 recover 肯定不陌生，但是你有没有想过。当我们执行了这两条语句之后。底层到底发生了什么事呢？前几天和同事刚好聊到相关的话题，发现其实大家对这块理解还是比较模糊的。希望这篇文章能够从更深入的角度告诉你为什么，它到底做了什么事？
[&amp;hellip;] func main() { panic(&amp;#34;EDDYCJY.&amp;#34;) } 输出结 &amp;hellip;">
    <meta property="og:description" content="作为一个 gophper，我相信你对于 panic 和 recover 肯定不陌生，但是你有没有想过。当我们执行了这两条语句之后。底层到底发生了什么事呢？前几天和同事刚好聊到相关的话题，发现其实大家对这块理解还是比较模糊的。希望这篇文章能够从更深入的角度告诉你为什么，它到底做了什么事？
[&amp;hellip;] func main() { panic(&amp;#34;EDDYCJY.&amp;#34;) } 输出结 &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.asia/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.asia/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.asia/js/jquery.min.js"></script>




<link href="https://www.lican.asia/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>

        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/posts/go/panic/2019-05-21-panic-and-recover/">深入理解 Go panic and recover</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2019/05/21</a>
                                            
                                            
                                            
                                            By 
                                            <a class="author" href="javascript:;">煎鱼</a></div>
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>作为一个 gophper，我相信你对于 <code>panic</code> 和 <code>recover</code> 肯定不陌生，但是你有没有想过。当我们执行了这两条语句之后。底层到底发生了什么事呢？前几天和同事刚好聊到相关的话题，发现其实大家对这块理解还是比较模糊的。希望这篇文章能够从更深入的角度告诉你为什么，它到底做了什么事？</p>
<h2 id="思考">思考</h2>
<h3 id="一为什么会中止运行">一、为什么会中止运行</h3>
<pre tabindex="0"><code>func main() {
	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go
panic: EDDYCJY.

goroutine 1 [running]:
main.main()
	/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2
</code></pre><p>请思考一下，为什么执行 <code>panic</code> 后会导致应用程序运行中止？（而不是单单说执行了 <code>panic</code> 所以就结束了这么含糊）</p>
<h3 id="二为什么不会中止运行">二、为什么不会中止运行</h3>
<pre tabindex="0"><code>func main() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf(&#34;recover: %v&#34;, err)
		}
	}()

	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go 
2019/05/11 23:39:47 recover: EDDYCJY.
</code></pre><p>请思考一下，为什么加上 <code>defer</code> + <code>recover</code> 组合就可以保护应用程序？</p>
<h3 id="三不设置-defer-行不">三、不设置 defer 行不</h3>
<p>上面问题二是 <code>defer</code> + <code>recover</code> 组合，那我去掉 <code>defer</code> 是不是也可以呢？如下：</p>
<pre tabindex="0"><code>func main() {
	if err := recover(); err != nil {
		log.Printf(&#34;recover: %v&#34;, err)
	}

	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go
panic: EDDYCJY.

goroutine 1 [running]:
main.main()
	/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:10 +0xa1
exit status 2
</code></pre><p>竟然不行，啊呀毕竟入门教程都写的 <code>defer</code> + <code>recover</code> 组合 “万能” 捕获。但是为什么呢。去掉 <code>defer</code> 后为什么就无法捕获了？</p>
<p>请思考一下，为什么需要设置 <code>defer</code> 后 <code>recover</code> 才能起作用？</p>
<p>同时你还需要仔细想想，我们设置 <code>defer</code> + <code>recover</code> 组合后就能无忧无虑了吗，各种 “乱” 写了吗？</p>
<h3 id="四为什么起个-goroutine-就不行">四、为什么起个 goroutine 就不行</h3>
<pre tabindex="0"><code>func main() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				log.Printf(&#34;recover: %v&#34;, err)
			}
		}()
	}()

	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go 
panic: EDDYCJY.

goroutine 1 [running]:
main.main()
	/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:14 +0x51
exit status 2
</code></pre><p>请思考一下，为什么新起了一个 <code>Goroutine</code> 就无法捕获到异常了？到底发生了什么事&hellip;</p>
<h2 id="源码">源码</h2>
<p>接下来我们将带着上述 4+1 个小思考题，开始对源码的剖析和分析，尝试从阅读源码中找到思考题的答案和更多为什么</p>
<h3 id="数据结构">数据结构</h3>
<pre tabindex="0"><code>type _panic struct {
	argp      unsafe.Pointer
	arg       interface{} 
	link      *_panic 
	recovered bool
	aborted   bool 
}
</code></pre><p>在 <code>panic</code> 中是使用 <code>_panic</code> 作为其基础单元的，每执行一次 <code>panic</code> 语句，都会创建一个 <code>_panic</code>。它包含了一些基础的字段用于存储当前的 <code>panic</code> 调用情况，涉及的字段如下：</p>
<ul>
<li>argp：指向 <code>defer</code> 延迟调用的参数的指针</li>
<li>arg：<code>panic</code> 的原因，也就是调用 <code>panic</code> 时传入的参数</li>
<li>link：指向上一个调用的 <code>_panic</code></li>
<li>recovered：<code>panic</code> 是否已经被处理，也就是是否被 <code>recover</code></li>
<li>aborted：<code>panic</code> 是否被中止</li>
</ul>
<p>另外通过查看 <code>link</code> 字段，可得知其是一个链表的数据结构，如下图：</p>
<p><img src="http://wx3.sinaimg.cn/large/006fVPCvly1g2muc73jp1j30hc099q2x.jpg" alt="image"></p>
<h3 id="恐慌-panic">恐慌 panic</h3>
<pre tabindex="0"><code>func main() {
	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go
panic: EDDYCJY.

goroutine 1 [running]:
main.main()
	/Users/eddycjy/go/src/github.com/EDDYCJY/awesomeProject/main.go:4 +0x39
exit status 2
</code></pre><p>我们去反查一下 <code>panic</code> 处理具体逻辑的地方在哪，如下：</p>
<pre tabindex="0"><code>$ go tool compile -S main.go
&#34;&#34;.main STEXT size=66 args=0x0 locals=0x18
	0x0000 00000 (main.go:23)	TEXT	&#34;&#34;.main(SB), ABIInternal, $24-0
	0x0000 00000 (main.go:23)	MOVQ	(TLS), CX
	0x0009 00009 (main.go:23)	CMPQ	SP, 16(CX)
	...
	0x002f 00047 (main.go:24)	PCDATA	$2, $0
	0x002f 00047 (main.go:24)	MOVQ	AX, 8(SP)
	0x0034 00052 (main.go:24)	CALL	runtime.gopanic(SB)
</code></pre><p>显然汇编代码直指内部实现是 <code>runtime.gopanic</code>，我们一起来看看这个方法做了什么事，如下（省略了部分）：</p>
<pre tabindex="0"><code>func gopanic(e interface{}) {
	gp := getg()
	...
	var p _panic
	p.arg = e
	p.link = gp._panic
	gp._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p)))
    
	for {
		d := gp._defer
		if d == nil {
			break
		}

		// defer...
		...
		d._panic = (*_panic)(noescape(unsafe.Pointer(&amp;p)))

		p.argp = unsafe.Pointer(getargp(0))
		reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
		p.argp = nil

		// recover...
		if p.recovered {
			...
			mcall(recovery)
			throw(&#34;recovery failed&#34;) // mcall should not return
		}
	}

	preprintpanics(gp._panic)

	fatalpanic(gp._panic) // should not return
	*(*int)(nil) = 0      // not reached
}
</code></pre><ul>
<li>获取指向当前 <code>Goroutine</code> 的指针</li>
<li>初始化一个 <code>panic</code> 的基本单位 <code>_panic</code> 用作后续的操作</li>
<li>获取当前 <code>Goroutine</code> 上挂载的 <code>_defer</code>（数据结构也是链表）</li>
<li>若当前存在 <code>defer</code> 调用，则调用 <code>reflectcall</code> 方法去执行先前 <code>defer</code> 中延迟执行的代码，若在执行过程中需要运行 <code>recover</code> 将会调用 <code>gorecover</code> 方法</li>
<li>结束前，使用 <code>preprintpanics</code> 方法打印出所涉及的 <code>panic</code> 消息</li>
<li>最后调用 <code>fatalpanic</code> 中止应用程序，实际是执行 <code>exit(2)</code> 进行最终退出行为的</li>
</ul>
<p>通过对上述代码的执行分析，可得知 <code>panic</code> 方法实际上就是处理当前 <code>Goroutine(g)</code> 上所挂载的 <code>._panic</code> 链表（所以无法对其他 <code>Goroutine</code> 的异常事件响应），然后对其所属的 <code>defer</code> 链表和 <code>recover</code> 进行检测并处理，最后调用退出命令中止应用程序</p>
<h3 id="无法恢复的恐慌-fatalpanic">无法恢复的恐慌 fatalpanic</h3>
<pre tabindex="0"><code>func fatalpanic(msgs *_panic) {
	pc := getcallerpc()
	sp := getcallersp()
	gp := getg()
	var docrash bool

	systemstack(func() {
		if startpanic_m() &amp;&amp; msgs != nil {
		    ...
			printpanics(msgs)
		}

		docrash = dopanic_m(gp, pc, sp)
	})

	systemstack(func() {
		exit(2)
	})

	*(*int)(nil) = 0
}
</code></pre><p>我们看到在异常处理的最后会执行该方法，似乎它承担了所有收尾工作。实际呢，它是在最后对程序执行 <code>exit</code> 指令来达到中止运行的作用，但在结束前它会通过 <code>printpanics</code> 递归输出所有的异常消息及参数。代码如下：</p>
<pre tabindex="0"><code>func printpanics(p *_panic) {
	if p.link != nil {
		printpanics(p.link)
		print(&#34;\t&#34;)
	}
	print(&#34;panic: &#34;)
	printany(p.arg)
	if p.recovered {
		print(&#34; [recovered]&#34;)
	}
	print(&#34;\n&#34;)
}
</code></pre><p>所以不要以为所有的异常都能够被 <code>recover</code> 到，实际上像 <code>fatal error</code> 和 <code>runtime.throw</code> 都是无法被 <code>recover</code> 到的，甚至是 oom 也是直接中止程序的，也有反手就给你来个 <code>exit(2)</code> 教做人。因此在写代码时你应该要相对注意些，“恐慌” 是存在无法恢复的场景的</p>
<h3 id="恢复-recover">恢复 recover</h3>
<pre tabindex="0"><code>func main() {
	defer func() {
		if err := recover(); err != nil {
			log.Printf(&#34;recover: %v&#34;, err)
		}
	}()

	panic(&#34;EDDYCJY.&#34;)
}
</code></pre><p>输出结果：</p>
<pre tabindex="0"><code>$ go run main.go 
2019/05/11 23:39:47 recover: EDDYCJY.
</code></pre><p>和预期一致，成功捕获到了异常。但是 <code>recover</code> 是怎么恢复 <code>panic</code> 的呢？再看看汇编代码，如下：</p>
<pre tabindex="0"><code>$ go tool compile -S main.go
&#34;&#34;.main STEXT size=110 args=0x0 locals=0x18
	0x0000 00000 (main.go:5)	TEXT	&#34;&#34;.main(SB), ABIInternal, $24-0
	...
	0x0024 00036 (main.go:6)	LEAQ	&#34;&#34;.main.func1·f(SB), AX
	0x002b 00043 (main.go:6)	PCDATA	$2, $0
	0x002b 00043 (main.go:6)	MOVQ	AX, 8(SP)
	0x0030 00048 (main.go:6)	CALL	runtime.deferproc(SB)
	...
	0x0050 00080 (main.go:12)	CALL	runtime.gopanic(SB)
	0x0055 00085 (main.go:12)	UNDEF
	0x0057 00087 (main.go:6)	XCHGL	AX, AX
	0x0058 00088 (main.go:6)	CALL	runtime.deferreturn(SB)
	...
	0x0022 00034 (main.go:7)	MOVQ	AX, (SP)
	0x0026 00038 (main.go:7)	CALL	runtime.gorecover(SB)
	0x002b 00043 (main.go:7)	PCDATA	$2, $1
	0x002b 00043 (main.go:7)	MOVQ	16(SP), AX
	0x0030 00048 (main.go:7)	MOVQ	8(SP), CX
	...
	0x0056 00086 (main.go:8)	LEAQ	go.string.&#34;recover: %v&#34;(SB), AX
	...
	0x0086 00134 (main.go:8)	CALL	log.Printf(SB)
	...
</code></pre><p>通过分析底层调用，可得知主要是如下几个方法：</p>
<ul>
<li>runtime.deferproc</li>
<li>runtime.gopanic</li>
<li>runtime.deferreturn</li>
<li>runtime.gorecover</li>
</ul>
<p>在上小节中，我们讲述了简单的流程，<code>gopanic</code> 方法会调用当前 <code>Goroutine</code> 下的 <code>defer</code> 链表，若 <code>reflectcall</code> 执行中遇到 <code>recover</code> 就会调用 <code>gorecover</code> 进行处理，该方法代码如下：</p>
<pre tabindex="0"><code>func gorecover(argp uintptr) interface{} {
	gp := getg()
	p := gp._panic
	if p != nil &amp;&amp; !p.recovered &amp;&amp; argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	}
	return nil
}
</code></pre><p>这代码，看上去挺简单的，核心就是修改 <code>recovered</code> 字段。该字段是用于标识当前 <code>panic</code> 是否已经被 <code>recover</code> 处理。但是这和我们想象的并不一样啊，程序是怎么从 <code>panic</code> 流转回去的呢？是不是在核心方法里处理了呢？我们再看看 <code>gopanic</code> 的代码，如下：</p>
<pre tabindex="0"><code>func gopanic(e interface{}) {
	...
	for {
		// defer...
		...
		pc := d.pc
		sp := unsafe.Pointer(d.sp) // must be pointer so it gets adjusted during stack copy
		freedefer(d)
		
		// recover...
		if p.recovered {
			atomic.Xadd(&amp;runningPanicDefers, -1)

			gp._panic = p.link
			for gp._panic != nil &amp;&amp; gp._panic.aborted {
				gp._panic = gp._panic.link
			}
			if gp._panic == nil { 
				gp.sig = 0
			}

			gp.sigcode0 = uintptr(sp)
			gp.sigcode1 = pc
			mcall(recovery)
			throw(&#34;recovery failed&#34;) 
		}
	}
    ...
}
</code></pre><p>我们回到 <code>gopanic</code> 方法中再仔细看看，发现实际上是包含对 <code>recover</code> 流转的处理代码的。恢复流程如下：</p>
<ul>
<li>判断当前 <code>_panic</code> 中的 <code>recover</code> 是否已标注为处理</li>
<li>从 <code>_panic</code> 链表中删除已标注中止的 <code>panic</code> 事件，也就是删除已经被恢复的 <code>panic</code> 事件</li>
<li>将相关需要恢复的栈帧信息传递给 <code>recovery</code> 方法的 <code>gp</code> 参数（每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量）</li>
<li>执行 <code>recovery</code> 进行恢复动作</li>
</ul>
<p>从流程来看，最核心的是 <code>recovery</code> 方法。它承担了异常流转控制的职责。代码如下：</p>
<pre tabindex="0"><code>func recovery(gp *g) {
	sp := gp.sigcode0
	pc := gp.sigcode1

	if sp != 0 &amp;&amp; (sp &lt; gp.stack.lo || gp.stack.hi &lt; sp) {
		print(&#34;recover: &#34;, hex(sp), &#34; not in [&#34;, hex(gp.stack.lo), &#34;, &#34;, hex(gp.stack.hi), &#34;]\n&#34;)
		throw(&#34;bad recovery&#34;)
	}

	gp.sched.sp = sp
	gp.sched.pc = pc
	gp.sched.lr = 0
	gp.sched.ret = 1
	gogo(&amp;gp.sched)
}
</code></pre><p>粗略一看，似乎就是很简单的设置了一些值？但实际上设置的是编译器中伪寄存器的值，常常被用于维护上下文等。在这里我们需要结合 <code>gopanic</code> 方法一同观察 <code>recovery</code> 方法。它所使用的栈指针 <code>sp</code> 和程序计数器 <code>pc</code> 是由当前 <code>defer</code> 在调用流程中的 <code>deferproc</code> 传递下来的，因此实际上最后是通过 <code>gogo</code> 方法跳回了 <code>deferproc</code> 方法。另外我们注意到：</p>
<pre tabindex="0"><code>gp.sched.ret = 1
</code></pre><p>在底层中程序将 <code>gp.sched.ret</code> 设置为了 1，也就是<strong>没有实际调用</strong> <code>deferproc</code> 方法，直接修改了其返回值。意味着默认它已经处理完成。直接转移到 <code>deferproc</code> 方法的下一条指令去。至此为止，异常状态的流转控制就已经结束了。接下来就是继续走 <code>defer</code> 的流程了</p>
<p>为了验证这个想法，我们可以看一下核心的跳转方法 <code>gogo</code> ，代码如下：</p>
<pre tabindex="0"><code>// void gogo(Gobuf*)
// restore state from Gobuf; longjmp
TEXT runtime·gogo(SB),NOSPLIT,$8-4
	MOVW	buf+0(FP), R1
	MOVW	gobuf_g(R1), R0
	BL	setg&lt;&gt;(SB)

	MOVW	gobuf_sp(R1), R13	// restore SP==R13
	MOVW	gobuf_lr(R1), LR
	MOVW	gobuf_ret(R1), R0
	MOVW	gobuf_ctxt(R1), R7
	MOVW	$0, R11
	MOVW	R11, gobuf_sp(R1)	// clear to help garbage collector
	MOVW	R11, gobuf_ret(R1)
	MOVW	R11, gobuf_lr(R1)
	MOVW	R11, gobuf_ctxt(R1)
	MOVW	gobuf_pc(R1), R11
	CMP	R11, R11 // set condition codes for == test, needed by stack split
	B	(R11)
</code></pre><p>通过查看代码可得知其主要作用是从 <code>Gobuf</code> 恢复状态。简单来讲就是将寄存器的值修改为对应 <code>Goroutine(g)</code> 的值，而在文中讲了很多次的 <code>Gobuf</code>，如下：</p>
<pre tabindex="0"><code>type gobuf struct {
	sp   uintptr
	pc   uintptr
	g    guintptr
	ctxt unsafe.Pointer
	ret  sys.Uintreg
	lr   uintptr
	bp   uintptr
}
</code></pre><p>讲道理，其实它存储的就是 <code>Goroutine</code> 切换上下文时所需要的一些东西</p>
<h2 id="拓展">拓展</h2>
<pre tabindex="0"><code>const(
	OPANIC       // panic(Left)
	ORECOVER     // recover()
	...
)
...
func walkexpr(n *Node, init *Nodes) *Node {
    ...
	switch n.Op {
	default:
		Dump(&#34;walk&#34;, n)
		Fatalf(&#34;walkexpr: switch 1 unknown op %+S&#34;, n)

	case ONONAME, OINDREGSP, OEMPTY, OGETG:
	case OTYPE, ONAME, OLITERAL:
	    ...
	case OPANIC:
		n = mkcall(&#34;gopanic&#34;, nil, init, n.Left)

	case ORECOVER:
		n = mkcall(&#34;gorecover&#34;, n.Type, init, nod(OADDR, nodfp, nil))
	...
}
</code></pre><p>实际上在调用 <code>panic</code> 和 <code>recover</code> 关键字时，是在编译阶段先转换为相应的 OPCODE 后，再由编译器转换为对应的运行时方法。并不是你所想像那样一步到位，有兴趣的小伙伴可以研究一下</p>
<h2 id="总结">总结</h2>
<p>本文主要针对 <code>panic</code> 和 <code>recover</code> 关键字进行了深入源码的剖析，而开头的 4+1 个思考题，就是希望您能够带着疑问去学习，达到事半功倍的功效</p>
<p>另外本文和 <code>defer</code> 有一定的关联性，因此需要有一定的基础知识。若刚刚看的时候这部分不理解，学习后可以再读一遍加深印象</p>
<p>在最后，现在的你可以回答这几个思考题了吗？说出来了才是真的懂 ：）</p>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/map/2019-04-07-why-map-no-order/">为什么遍历 Go map 是无序的</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/map/2019-03-24-map-assign/">深入理解 Go map：赋值和扩容迁移</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/map/2019-03-05-map-access/">深入理解 Go map：初始化和访问元素</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/slice/2019-01-06-why-slice-max/">Go Slice 最大容量大小是怎么来的</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.asia/posts/posts/go/pkg/2018-12-15-unsafe/">有点不安全却又一亮的 Go unsafe.Pointer</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            <li>
                                                <a href="/tags/%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90" title="View all posts tagged match">源码分析</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.asia/posts/posts/go/talk/2019-05-20-stack-heap/"
                                                    rel="prev">我要在栈上。不，你应该在堆上</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.asia/posts/posts/go/defer/2019-05-27-defer/">深入理解 Go defer</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.asia/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    <section id="qrcode" class="widget widget_media_image">
        <h2 class="widget-title">微信公众号</h2>
        <img width="258" height="258"
            src="https://www.lican.asia/wechat/lican.png"
            class="image wp-image-5514  attachment-full size-full" alt=""
            style="max-width: 100%; height: auto;"
            sizes="(max-width: 258px) 100vw, 258px">
    </section>
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.asia All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>